#include <pthread.h>
#include <sys/unistd.h>

#include <string>
#include <iostream>
#include <unordered_map>

using namespace std;

class Flyweight {
public:
    explicit Flyweight(const string &key) : key(key) {}

    virtual ~Flyweight() = default;;

protected:
    string key;

public:
    virtual void doSomeThing() = 0;
};

class FlyweightFactory {
public:
    FlyweightFactory() = default;

    virtual ~FlyweightFactory() {
        for (auto const &entry : flyweight_map) {
            delete entry.second;
        }
        flyweight_map.clear();
    };

protected:
    unordered_map<string, Flyweight *> flyweight_map;

public:
    virtual Flyweight *getFlyWeight(string key) = 0;
};

class ConcreteFlyweightA : public Flyweight {
public:
    explicit ConcreteFlyweightA(const string &key) : Flyweight(key) {}

    ~ConcreteFlyweightA() override = default;

public:
    void doSomeThing() override {
        cout << "Concrete Flyweight A Address Is " << this << endl;
        cout << "Concrete Flyweight A Key Is " << key << endl;
        cout << "Concrete Flyweight A Do Some Thing!" << endl;
    }
};

class ConcreteFlyweightAFactory : public FlyweightFactory {
public:
    ~ConcreteFlyweightAFactory() override = default;

private:
    static pthread_mutex_t mutex;

public:
    Flyweight *getFlyWeight(string key) override {
        if (flyweight_map.find(key) == flyweight_map.end()) {
            pthread_mutex_lock(&mutex);
            if (flyweight_map.find(key) == flyweight_map.end()) {
                flyweight_map[key] = new ConcreteFlyweightA(key);
            }
            pthread_mutex_unlock(&mutex);
        }
        return flyweight_map[key];
    }
};

pthread_mutex_t ConcreteFlyweightAFactory::mutex = PTHREAD_MUTEX_INITIALIZER;

class ConcreteFlyweightB : public Flyweight {
public:
    explicit ConcreteFlyweightB(const string &key) : Flyweight(key) {}

    ~ConcreteFlyweightB() override = default;

public:
    void doSomeThing() override {
        cout << "Concrete Flyweight B Address Is " << this << endl;
        cout << "Concrete Flyweight B Key Is " << key << endl;
        cout << "Concrete Flyweight B Do Some Thing!" << endl;
    }
};

class ConcreteFlyweightBFactory : public FlyweightFactory {
public:
    ~ConcreteFlyweightBFactory() override = default;

private:
    static pthread_mutex_t mutex;

public:
    Flyweight *getFlyWeight(string key) override {
        if (flyweight_map.find(key) == flyweight_map.end()) {
            pthread_mutex_lock(&mutex);
            if (flyweight_map.find(key) == flyweight_map.end()) {
                flyweight_map[key] = new ConcreteFlyweightB(key);
            }
            pthread_mutex_unlock(&mutex);
        }
        return flyweight_map[key];
    }
};

pthread_mutex_t ConcreteFlyweightBFactory::mutex = PTHREAD_MUTEX_INITIALIZER;

void test01() {
    Flyweight *instanceA = nullptr;
    Flyweight *instanceB = nullptr;
    Flyweight *instanceC = nullptr;
    FlyweightFactory *factory = nullptr;

    factory = new ConcreteFlyweightAFactory();

    instanceA = factory->getFlyWeight("KeyA");
    instanceB = factory->getFlyWeight("KeyB");
    instanceC = factory->getFlyWeight("KeyA");

    instanceA->doSomeThing();
    cout << "--------------------------------------------" << endl;
    instanceB->doSomeThing();
    cout << "--------------------------------------------" << endl;
    instanceC->doSomeThing();

    delete factory;
}


void test02() {
    Flyweight *instanceA = nullptr;
    Flyweight *instanceB = nullptr;
    Flyweight *instanceC = nullptr;
    FlyweightFactory *factory = nullptr;

    factory = new ConcreteFlyweightBFactory();

    instanceA = factory->getFlyWeight("KeyA");
    instanceB = factory->getFlyWeight("KeyB");
    instanceC = factory->getFlyWeight("KeyB");

    instanceA->doSomeThing();
    cout << "--------------------------------------------" << endl;
    instanceB->doSomeThing();
    cout << "--------------------------------------------" << endl;
    instanceC->doSomeThing();

    delete factory;
}


int main() {
    test01();
    cout << "--------------------------------------------" << endl;
    test02();
    return 0;
}